/*
    Forms.js
    Copyright (C) 2010, Donald W. Griffin
    All Rights Reserved
*/

Wdg.LiquidFormLayout = Ext.extend(Ext.layout.FormLayout, function () {

    var base;
    var measureEl;
    var foolLayout = 0;

    function fixLabelWidth (ct) {
        var max = 0;

        ct.items.each(function (item) {
            if (!item.fieldLabel || item.hideLabel)
                return;

            var width = getLabelWidth(item.fieldLabel);
            max = Math.max(max, width);
        });

        max += this.labelGap;
        if (ct.labelWidth == max)
            return;

        ct.labelWidth = max;

        var width = max + (Ext.isNumber(ct.labelPad) ? ct.labelPad : 5);

        Ext.apply(this, {
            labelAdjust: width,
            labelStyle: "width:" + max + "px;",
            elementStyle: "padding-left:" + width + "px"
        });

        ct.items.each(function (item) {
            if (!item.fieldLabel || item.hideLabel || !item.label)
                return;

            item.label.dom.style.width = max + "px";

            var el = item.label.dom.nextSibling;
            el.style.paddingLeft = width + "px";
        });

        runLayout.defer(1, this, [ct]);
    }

    function getLabelWidth (text) {
        measureEl.update(text+":");
        var size = measureEl.getSize();
        measureEl.update("");
        return size.width;
    }

    function init () {
        measureEl = new Ext.Element(document.createElement("label"));
        document.body.appendChild(measureEl.dom);

        measureEl.dom.style.width = "auto";
        measureEl.addClass("x-form-item-label");
        measureEl.position("absolute");
        measureEl.setLeftTop(-1000, -1000);
        measureEl.hide();
    }

    function runLayout (ct) {
        foolLayout = 1;
        try {
            ct.doLayout(false, true);
        } finally {
            foolLayout = 0;
        }
    }

    return {
        type: "wdg-liquidform",
        labelGap: 0,

        constructor: function () {
            base = Wdg.LiquidFormLayout.superclass;
            base.constructor.apply(this, arguments);

            if (!measureEl)
                init();
        },

        getLayoutTargetSize: function () {
            var ret = base.getLayoutTargetSize.call(this);
            ret.width += foolLayout;
            return ret;
        },

        onLayout : function(ct, target) {
            fixLabelWidth.call(this, ct);
            base.onLayout.call(this, ct, target);
        },

        setContainer : function(ct) {
            if (ct)
                fixLabelWidth.call(this, ct);

            return base.setContainer.call(this, ct);
        }
    };
}());

Wdg.regLayout(Wdg.LiquidFormLayout);

Wdg.Popup = Ext.extend(Ext.Layer, function () {
    
    var base;
    var copyProps = [
        "alignment",
        "baseCls",
        "maxHeight",
        "minHeight",
        "minWidth",
        "owner",
        "refEl",
        "resizable",
        "resizeHandles",
        "resizeHandleHeight",
        "syncFontEl",
        "title",
        "width",
        "height"
    ];

    return {
        heightAdj : 0,
        minHeight : 0,

        constructor: function (cfg, existingEl) {
            base = Wdg.Popup.superclass;
            base.constructor.call(this, cfg, existingEl);

            Ext.copyTo(this, cfg, copyProps);
            if (cfg.style)
                Ext.DomHelper.applyStyles(this, cfg.style);

            var h, w = this.width || Math.max(this.refEl.getWidth(), this.minWidth);

            var cls = this.baseCls;
            if (this.syncFontEl)
                this.setStyle("font-size", this.syncFontEl.getStyle("font-size"));

            if (this.title)
                this.header = this.createChild({
                    cls: cls+"-hd",
                    html: this.title
                });

            this.innerCt = this.createChild({ cls: cls+"-inner" });
            this.innerCt.setWidth(w - this.getFrameWidth("lr"));

            var view = this.owner.createView(this.innerCt);
            this.view = view;
            this.syncHeightAdj();

            if (view.autoHeight || view.autoWidth) {
                var fn = view.getSizeEl || view.getPositionEl;
                var vel = fn.call(view);

                if (view.autoHeight) {
                    h = this.heightAdj + vel.getHeight();
                    h += this.getFrameWidth("tb");
                }
                if (view.autoWidth) {
                    w = vel.getWidth();
                    w += this.getFrameWidth("lr");
                }
            }

            if (this.resizable) {
                this.resizer = new Ext.Resizable(this, {
                   pinned: true,
                   handles: this.resizeHandles
                });

                this.resizer.on("resize", this.onResizer, this);
                this.innerCt.setStyle("margin-bottom", this.resizeHandleHeight + "px");
                if (this.view.autoHeight)
                    h += this.resizeHandleHeight;
            }

            this.swallowEvent("mousewheel");
            this.setSize(w, h);
            this.restrictHeight();
        },

        destroy : function() {
            if (this.resizer)
                this.resizer.un("resize", this.onResizer, this);

            Ext.destroy(this.view, this.resizer, this.innerCt);
            this.resizer = this.view = this.innerCt = null;

            base.destroy.call(this);
        },

        onResize: function (w, h) {
            if (typeof(w) !== "undefined" && !isNaN(w) && this.width !== w) {
                this.width = w;
                if (!this.view.autoWidth)
                    this.innerCt.setWidth(w - this.getFrameWidth("lr"));
            }
            if (typeof(h) !== "undefined" && !isNaN(h) && this.height !== h) {
                this.height = h;
                if (!this.view.autoHeight) {
                    h -= this.heightAdj + this.getFrameWidth("tb");
                    if (this.resizable)
                        h -= this.resizeHandleHeight;
                    this.innerCt.setHeight(h);
                }
            }
            this.sync();
        },

        onResizer: function (r, w, h) {
            this.maxHeight = h - this.resizeHandleHeight -
                this.getFrameWidth("tb") - this.heightAdj;

            this.onResize(w, h);
            this.restrictHeight();
        },

        restrictHeight : function() {
            this.innerCt.dom.style.height = "";
            var inner = this.innerCt.dom,
                pad = this.getFrameWidth("tb") +
                    (this.resizable ? this.resizeHandleHeight : 0) + this.heightAdj,
                h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),
                ha = this.owner.getPosition()[1] - Ext.getBody().getScroll().top,
                hb = Ext.lib.Dom.getViewHeight() - ha - this.owner.getSize().height,
                space = Math.max(ha, hb, this.minHeight) - this.shadowOffset - pad - 5;

            h = Math.min(h, space, this.maxHeight);

            this.innerCt.setHeight(h);
            this.beginUpdate();
            this.setHeight(h+pad);
            this.alignTo(this.refEl, this.alignment);
            this.endUpdate();
        },

        setHeight : function (h) {
            base.setHeight.call(this, h);
            this.onResize(undefined, h);
        },

        setSize : function (w, h) {
            base.setSize.call(this, w, h);
            this.onResize(w, h);
        },

        setWidth : function (w) {
            base.setWidth.call(this, w);
            this.onResize(w, undefined);
        },

        syncHeightAdj : function () {
            if (this.header)
                this.heightAdj = this.header.getHeight();
        }
    };
}());

Wdg.DropDownField = Ext.extend(Ext.form.TwinTriggerField, function () {

    var base;

    return {
        dropDownBaseCls: "x-combo-list",

        dropDownCls: "",

        /**
        @cfg {String/Array} listAlign A valid anchor position value. See <tt>{@link Ext.Element#alignTo}</tt> for details
        on supported anchor positions and offsets. To specify x/y offsets as well, this value
        may be specified as an Array of <tt>{@link Ext.Element#alignTo}</tt> method arguments.</p>
        <pre><code>[ 'tl-bl?', [6,0] ]</code></pre>(defaults to <tt>'tl-bl?'</tt>)
        */
        dropDownAlign : "tl-bl?",

        // dropDownWidth
        dropDownMaxHeight : 300,
        dropDownMinHeight : 90,
        dropDownMinWidth : 70,
        dropDownResizable : true,
        dropDownResizeHandles : "se",
        dropDownResizeHandleHeight: 6,

        dropDownStyle: null,
        dropDownSyncFont: true,
        dropDownTitle: null,

        /**
        @cfg {Boolean} lazyInit <tt>true</tt> to not initialize the list for this combo until the field is focused
        (defaults to <tt>true</tt>)
        */
        lazyInit : true,

        trigger1Class : "x-form-clear-trigger",
        trigger2Class : "x-form-arrow-trigger",

        /**
        @cfg {Boolean/String} shadow <tt>true</tt> or <tt>"sides"</tt> for the default effect, <tt>"frame"</tt> for
        4-way shadow, and <tt>"drop"</tt> for bottom-right
        */
        shadow : "sides",

        initComponent : function () {
            base = Wdg.DropDownField.superclass;
            base.initComponent.call(this);

            this.addEvents(
                /**
                @event expand
                Fires when the dropdown list is expanded
                @param {Wdg.DropDownField} combo This field component
                */
                "expand",

                /**
                @event collapse
                Fires when the dropdown list is collapsed
                @param {Wdg.DropDownField} combo This field component
                */
                "collapse"
            );
        },

        onRender : function(ct, position) {
            base.onRender.call(this, ct, position);

            if (Ext.isGecko)
                this.el.dom.setAttribute("autocomplete", "off");

            if (this.lazyInit)
                this.on("focus", this.initDropDown, this, { single: true });
            else
                this.initDropDown();
        },

        onDestroy : function() {
            Ext.destroy(this.popup);
            this.popup = null;

            base.onDestroy.call(this);
        },

        initDropDown : function () {
            var cfg = this.getDropDownConfig();
            this.popup = new Wdg.Popup(cfg);
        },

        getDropDownClass : function () {
            var cls = this.dropDownBaseCls + " " + this.dropDownCls;
            return cls;
        },

        getDropDownConfig : function () {
            var parent = this.getDropDownParent();

            return {
                id: this.id + "_popup",
                alignment: this.dropDownAlign,
                baseCls: this.dropDownBaseCls,
                cls: this.getDropDownClass(),
                maxHeight: this.dropDownMaxHeight,
                minHeight: this.dropDownMinHeight,
                minWidth: this.dropDownMinWidth,
                owner: this,
                parentEl: parent,
                shadow: this.shadow,
                constrain: false,
                refEl: this.wrap,
                resizable: this.resizable,
                resizeHandles: this.dropDownResizeHandles,
                resizeHandleHeight: this.dropDownResizeHandleHeight,
                style: this.dropDownStyle,
                syncFontEl: this.dropDownSyncFont ? this.el : null,
                title: this.dropDownTitle,
                width: this.dropDownWidth,
                zindex: this.getZIndex(parent)
            };
        },

        getDropDownParent : function () {
            return Ext.getBody();
        },

        getZIndex : function (parent) {
            if (!parent)
                parent = this.getDropDownParent();
            var zindex = parseInt(Ext.fly(parent).getStyle("z-index"), 10);
            if (!zindex && this.ownerCt)
                this.findParentBy(function(ct) {
                    zindex = parseInt(ct.getPositionEl().getStyle("z-index"), 10);
                    return !!zindex;
                });
            return (zindex || 12000) + 5;
        },

        doResize: function (w) {
            if (Ext.isDefined(this.dropDownWidth) || this.popup.view.autoWidth)
                return;

            var lw = Math.max(w, this.dropDownMinWidth);
            this.popup.setWidth(lw);
        },

        onResize : function (w, h) {
            base.onResize.call(this, w, h);

            if (!isNaN(w) && this.isVisible() && this.popup)
                this.doResize(w);
            else
                this.bufferSize = w;
        },

        /**
        Returns true if the dropdown list is expanded, else false.
        */
        isExpanded : function () {
            return this.popup && this.popup.isVisible();
        },

        /**
        Hides the dropdown if it is currently expanded. Fires the collapse event
        on completion.
        */
        collapse : function () {
            if (!this.isExpanded())
                return;

            this.popup.hide();

            var doc = Ext.getDoc();
            doc.un("mousewheel", this.collapseIf, this);
            doc.un("mousedown", this.collapseIf, this);

            this.fireEvent("collapse", this);
        },

        collapseIf : function (e) {
            if (!this.isDestroyed && !e.within(this.wrap) && !e.within(this.popup))
                this.collapse();
        },

        /**
        Expands the dropdown list if it is currently hidden. Fires the {@link #expand} event on completion.
        */
        expand : function() {
            if (this.isExpanded() || !this.hasFocus)
                return;

            this.popup.syncHeightAdj();

            if (this.bufferSize) {
                this.doResize(this.bufferSize);
                delete this.bufferSize;
            }

            this.popup.alignTo(this.el, this.dropDownAlign);
            this.popup.setZIndex(this.getZIndex()); // can change
            this.popup.show();

            if (Ext.isGecko2)
                this.innerPopup.setOverflow("auto"); // necessary for FF 2.0/Mac

            this.mon(Ext.getDoc(), {
                mousedown: this.collapseIf,
                mousewheel: this.collapseIf,
                scope: this
            });

            this.fireEvent("expand", this);
        },

        onClearTriggerClick : function () {
            // TODO
        },

        onExpandTriggerClick : function () {
            if (this.readOnly || this.disabled)
                return;

            if (this.isExpanded()) {
                this.collapse();
                this.el.focus();
            } else {
                this.onFocus({});
                this.startExpand();
                this.el.focus();
            }
        },

        onTrigger1Click : function () {
            this.onClearTriggerClick();
        },

        onTrigger2Click : function () {
            this.onExpandTriggerClick();
        }
    };
}());

/**

{{{
      AM     PM
    +----+ +----+
    | 12 | | 12 |    00 01 02 03 04
    |  1 | |  1 |    05 06 07 08 09
    |  2 | |  2 |    10 11 12 13 14
    |  3 | |  3 |    15 16 17 18 19
    |  4 | |  4 |    20 21 22 23 24
    |  5 | |  5 |    25 26 27 28 29
    |  6 | |  6 |    30 31 32 33 34
    |  7 | |  7 |    35 36 37 38 39
    |  8 | |  8 |    40 41 42 43 44
    |  9 | |  9 |    45 46 47 48 49
    | 10 | | 10 |    50 51 52 53 54
    | 11 | | 11 |    55 56 57 58 59
    +----+ +----+
}}}
*/
Wdg.TimePalette = Ext.extend(Ext.BoxComponent, function () {

    var base;

    return {
        xtype : "wdg-timepalette",
        baseCls : "wdg-timepalette",
        hour : 4,
        minutes : 30,
        titleAM : "AM",
        titlePM : "PM",
        titleMinutes : "Minutes",
        showAllMinutes : true,
        showTitles : true,

        initComponent : function () {
            base = Wdg.TimePalette.superclass;
            base.initComponent.call(this);

            this.addEvents(
                "select"
            );
        },

        afterRender : function () {
            base.afterRender.apply(this, arguments);
            this.initEvents();
        },

        buildTable : function () {
            var ret = ['<table id="',this.id,'" class="', this.baseCls, '-table">'];
            var cls = this.baseCls + "-minute ", minutes = this.showAllMinutes ? 5 : 1;
            var minuteCls1 = cls + this.baseCls + "-minute-major";
            var minuteCls2 = cls + this.baseCls + "-minute-minor";
            var hrCls = this.baseCls + "-hr ", baseId = this.id + "_";
            var amCls = hrCls + this.baseCls + "-amhr";
            var pmCls = hrCls + this.baseCls + "-pmhr";

            if (this.showTitles) {
                ret.push('<tr class="', this.baseCls, '-hd">');
                ret.push('<th class="', this.baseCls, '-hd">',this.titleAM,'</th>');
                ret.push('<th class="', this.baseCls, '-hd">',this.titlePM,'</th>');
                ret.push('<th class="', this.baseCls, '-gap"></th>');
                ret.push('<th class="', this.baseCls, '-hd" colspan="',minutes,'">',
                    this.titleMinutes,'</th>');
                ret.push('</tr>');
            }

            for (var hr = 12, i = 0; i < 12; ++i) {
                ret.push('<tr class="', this.baseCls, '-row');
                if (i % 2)
                    ret.push(' ', this.baseCls, '-rowAlt');
                ret.push('">');
                cls = amCls;
                if (i === this.hour)
                    cls += " " + this.baseCls + "-sel-hr";
                ret.push('<td class="', cls, '" id="',baseId,"hr",i,'" tabindex="0">',
                    hr, '</td>');
                cls = pmCls;
                if (i+12 === this.hour)
                    cls += " " + this.baseCls + "-sel-hr";
                ret.push('<td class="', cls, '" id="',baseId,"hr",i+12,'" tabindex="0">',
                    hr, '</td>');
                ret.push('<td class="', this.baseCls, '-gap"></td>');
                cls = minuteCls1;
                for (var j = 0; j < minutes; ++j) {
                    var min = i * 5 + j;
                    if (min === this.minutes)
                        cls += " " + this.baseCls + "-sel-min";
                    min = Wdg.zeroPad(min, 2);
                    ret.push('<td class="',cls,'" id="',baseId,"mn",min,'" tabindex="0">:',
                        min,'</td>');
                    cls = minuteCls2;
                }
                ret.push('</tr>');
                hr = i+1;
            }

            ret.push('</table>');
            return ret.join("");
        },

        getHour : function () {
            return this.hour;
        },

        setHour : function (h) {
            if (h === this.hour)
                return this;

            var id = this.getCellId(true, this.hour), cls = this.baseCls + "-sel-hr";
            if (id)
                Ext.fly(id).removeClass(cls);

            id = this.getCellId(true, this.hour = h);
            if (id)
                Ext.fly(id).addClass(cls);
            return this;
        },

        getMinutes : function () {
            return this.minutes;
        },

        setMinutes : function (m) {
            if (m === this.minutes)
                return this;

            var id = this.getCellId(false, this.minutes), cls = this.baseCls + "-sel-min";
            if (id)
                Ext.fly(id).removeClass(cls);

            id = this.getCellId(false, this.minutes = m);
            if (id)
                Ext.fly(id).addClass(cls);
            return this;
        },

        getTime : function () {
            if (this.hour === null || this.minutes === null)
                return null;
            return this.hour * 60 + this.minutes;
        },

        setTime : function (t) {
            this.setHour((t === null) ? null : Math.floor(t / 60));
            this.setMinutes((t === null) ? null : (t % 60));
            return this;
        },

        doSelect : function (e, commit) {
            var c = this.getCellInfo(e), v = c.value;
            if (c.hours) {
                this.setHour(v);
                if (!commit || this.minutes === null) {
                    v = (this.minutes === null) ? ((v % 12) * 5) : this.minutes;
                    this.focusCellId(this.getCellId(false, v));
                    return false;
                }
            } else {
                this.setMinutes(v);
                if (!commit || this.hour === null) {
                    v = (this.hour === null) ? Math.floor(v / 5) : this.hour;
                    this.focusCellId(this.getCellId(true, v));
                    return false;
                }
            }
            return true;
        },

        focusCellId : function (id) {
            if (id) {
                var f = Ext.fly(id);
                if (f)
                    f.focus();
            }
        },

        getCellId : function (hours, val) {
            if (val < 0 || val === null)
                return null;
            var id = this.id;
            if (hours) {
                if (val > 23)
                    return null;
                id += "_hr" + val;
            } else {
                if (val > 59)
                    return null;
                id += "_mn" + Wdg.zeroPad(val, 2);
            }
            return id;
        },

        getCellInfo : function (e) {
            var t = e.getTarget(), s = t.id, i = s.lastIndexOf('_');
            var v = parseInt(s.substring(i+3), 10);
            return {
                hours: (s.charAt(i+1) === 'h'),
                prefx: s.substring(0, i+3),
                value: v
            };
        },

        getSizeEl : function () {
            return this.sizeEl;
        },

        initEvents : function () {
            this.mon(this.el, "click", this.onClick, this);
            this.mon(this.el, "mouseenter", this.onHoverIn, this);
            this.mon(this.el, "mouseleave", this.onHoverOut, this);
            this.mon(this.el, "mousemove", this.onMouseMove, this);
            this.mon(this.el, "keypress", this.onKeyPress, this);

            this.keyNav = new Ext.KeyNav(this.el, {
                left : this.onNavLeft,
                right : this.onNavRight,
                up : this.onNavUp,
                down : this.onNavDown,
                enter : this.onNavEnter,
                scope : this
            });
        },

        onClick : function (e) {
            var c = this.getCellInfo(e), v = c.value;
            if (c.hours) {
                this.setHour(v);
            } else {
                this.setMinutes(v);
                if (this.hour !== null) {
                    this.fireEvent("select", this);
                }
            }
        },

        onHoverIn : function (e) {
            this.mouseIn = true;
        },

        onHoverOut : function (e) {
            this.mouseIn = false;
            this.setOver(null);
        },

        onKeyPress : function (e) {
            var k = e.getKey();
            if (k == e.SPACE)
                this.doSelect(e, false);
        },

        onMouseMove : function (e) {
            if (this.mouseIn) {
                var t = e.getTarget(), over;
                if (Ext.fly(t).hasClass(this.baseCls + "-minute"))
                    over = t;
                else if (Ext.fly(t).hasClass(this.baseCls + "-hr"))
                    over = t;

                if (over ? this.overEl != over.id : this.overEl)
                    this.setOver(over);
            }
        },

        onNavEnter : function (e) {
            e.stopPropagation();
            if (this.doSelect(e, true)) {
                this.fireEvent("select", this);
            }
            return true;
        },

        onNavLeft : function (e) {
            var c = this.getCellInfo(e), v = c.value, h = c.hours;
            if (h)
                v -= 12;
            else if (v % 5 > 0)
                --v;
            else {
                h = true;
                v = (v / 5) + 12;
            }

            this.focusCellId(this.getCellId(h, v));
        },

        onNavRight : function (e) {
            var c = this.getCellInfo(e), v = c.value, h = c.hours;
            if (h) {
                if (v < 12)
                    v += 12;
                else {
                    h = false;
                    v = (v - 12) * 5;
                }
            } else if (v % 5 == 4)
                return;
            else
                ++v;

            this.focusCellId(this.getCellId(h, v));
        },

        onNavUp : function (e) {
            var c = this.getCellInfo(e), v = c.value - (c.hours ? 1 : 5);
            this.focusCellId(this.getCellId(c.hours, v));
        },

        onNavDown : function (e) {
            var c = this.getCellInfo(e), v = c.value + (c.hours ? 1 : 5);
            this.focusCellId(this.getCellId(c.hours, v));
        },

        onRender : function () {
            base.onRender.apply(this, arguments);

            var el = this.getContentTarget();
            var html = this.buildTable();
            html = Ext.DomHelper.markup(html);
            el.update(html);

            this.sizeEl = Ext.get(this.id);
        },

        setOver : function (over) {
            var overCls = this.baseCls + "-over";
            if (this.overEl)
                Ext.fly(this.overEl).removeClass(overCls);
            this.overEl = over && over.id;
            if (over)
                Ext.fly(over).addClass(overCls);
        }
    };
}()),

Wdg.reg(Wdg.TimePalette);

Wdg.TimeField = Ext.extend(Wdg.DropDownField, function () {

    var base;
    var dateObj = new Date();
    var timeRegex = /\s*([012]?\d)[:](\d{1,2})\s*(am|pm)?\s*/i;

    return {
        xtype : "wdg-timefield",
        resizable : false,
        timeFormat : "g:i A",

        initComponent : function () {
            base = Wdg.TimeField.superclass;
            base.initComponent.call(this);
        },

        createView : function (ct) {
            return new Wdg.TimePalette({
                applyTo: ct,
                autoHeight: true,
                autoWidth: true,
                ctCls: "wdg-timefield-popup",
                id: this.id + "_times",
                listeners: {
                    select: this.onSelect,
                    scope: this
                }
            });
        },

        formatValue : function (v) {
            if (v === null)
                return "";
            var i = parseInt(v,10), h = Math.floor(i / 60), m = i % 60;
            dateObj.setHours(h);
            dateObj.setMinutes(m);
            var s = dateObj.format(this.timeFormat);
            return s;
        },

        parseValue : function (v) {
            var t = typeof(v);
            if (t === "string") {
                var m = timeRegex.exec(v);
                if (m) {
                    var hr = parseInt(m[1],10), mn = parseInt(m[2],10);
                    if (m[3]) { // am/pm
                        if (hr == 12)
                            hr = 0;
                        if (m[3].charAt(0).toLowerCase() == 'p')
                            hr += 12;
                    }
                    return hr*60 + mn;
                }
            } else if (t === "number") {
                if (0 <= v && v < 24*60)
                    return v;
            }

            return null;
        },

        getValue : function () {
            var s = base.getValue.call(this);
            var v = this.parseValue(s);
            return v;
        },

        setValue : function (v) {
            var p = this.parseValue(v), s = this.formatValue(p);
            base.setValue.call(this, s);
            if (this.popup && p !== null)
                this.popup.view.setTime(p);
        },

        initEvents : function () {
            base.initEvents.call(this);

            this.keyNav = new Ext.KeyNav(this.el, {
                down: this.onDownKeyPress,
                //forceKeyDown: true,
                scope: this
            });
        },

        onDownKeyPress : function (e) {
            this.expand();
            var v = this.popup.view;
            v.focusCellId(v.getCellId(true, v.getHour() || 0));
        },

        onSelect : function () {
            var v = this.popup.view, t = v.getTime();
            this.collapse();
            this.setValue(t);
            this.el.focus();
        },

        startExpand : function () {
            this.expand();
        }
    };
}());

Wdg.reg(Wdg.TimeField);

// ###_aaa
(function (TF) {

    var P = TF.prototype;
    var slice = Array.prototype.slice;

    var patterns = {
        "#": "[0-9]",
        "a": "[a-zA-Z]",
        "_": "[a-zA-Z0-9]"
    }

    function parseMask (mask) {
        var filler = "_";
        if (typeof(mask) !== "string") {
            filler = mask.filler;
            mask = mask.pattern;
        }

        var buf = [], tests = [], len = mask.length, partialPos = len, firstNonMaskPos=-1;

        for (var i = 0, n = len; i < n; ++i) {
            var c = mask.charAt(i);
            buf.push(patterns[c] ? filler : c);
            if (c == '?') {
                --len;
                partialPos = i;
            } else if (patterns[c]) {
                tests.push(new RegExp(patterns[c]));
                if (firstNonMaskPos < 0)
                    firstNonMaskPos = tests.length - 1;
            } else {
                tests.push(null);
            }
        }

        return {
            buffer: buf,
            filler: filler,
            length: len,
            tests: tests,
            partialPos: partialPos,
            firstNonMaskPos: firstNonMaskPos
        };
    }

    function clearBuffer (start, end) {
        var mst = this._mst, len = mst.length, tests = mst.tests, buf = mst.buffer;
        for (var i = start; i < end && i < len; i++) {
            if (tests[i])
                buf[i] = mst.filler;
        }
    }

    function writeBuffer () {
        var s = this._mst.buffer.join("");
        this.setRawValues(s);
    }

    function hook (superFn, fn) {
        return function () {
            var args = slice.call(arguments, 0);
            args.unshift(superFn);
            return fn.apply(this, args);
        };
    }

    function checkVal (allow) {
        var mst=this._mst, len=mst.length, tests=mst.tests, buf=mst.buffer;
        var ppos=mst.partialPos;
        //try to place characters where they belong
        var val = this.getRawValue(), lastMatch = -1;
        for (var i = 0, pos = 0; i < len; i++) {
            if (tests[i]) {
                buf[i] = mst.filler;
                while (pos++ < val.length) {
                    var c = val.charAt(pos - 1);
                    if (tests[i].test(c)) {
                        buf[i] = c;
                        lastMatch = i;
                        break;
                    }
                }
                if (pos > val.length)
                    break;
            } else if (buf[i] == val[pos] && i!=ppos) {
                ++pos;
                lastMatch = i;
            }
        }
        if (!allow && lastMatch + 1 < ppos) {
            this.setRawValue("");
            clearBuffer.call(this, 0, len);
        } else if (allow || lastMatch + 1 >= ppos) {
            writeBuffer.call(this);
            if (!allow) {
                var s = this.getRawValue();
                s = s.substring(0, lastMatch + 1);
                this.setRawValue(s);
            }
        }
        return (ppos ? i : mst.firstNonMaskPos);
    }

    function seekNext(pos) {
        var mst=this._mst, len=mst.length, tests=mst.tests;
        while (++pos <= len && !tests[pos])
            {}
        return pos;
    }

    function shiftL (pos) {
        var mst=this._mst, len=mst.length, tests=mst.tests, buf=mst.buffer;
        while (!tests[pos] && --pos >= 0)
            {}
        for (var i = pos; i < len; i++) {
            if (tests[i]) {
                buf[i] = mst.filler;
                var j = seekNext.call(this, i);
                if (j < len && tests[i].test(buf[j])) {
                    buf[i] = buf[j];
                } else
                    break;
            }
        }
        writeBuffer.call(this);
        //TODO - input.caret(Math.max(mst.firstNonMaskPos, pos));
    }

    function shiftR (pos) {
        var mst=this._mst, len=mst.length, tests=mst.tests, buf=mst.buffer;
        for (var i = pos, c = mst.filler; i < len; i++) {
            if (tests[i]) {
                var j = seekNext.call(this, i), t = buf[i];
                buf[i] = c;
                if (j < len && tests[j].test(t))
                    c = t;
                else
                    break;
            }
        }
    }

    Ext.override(TF, {
        initComponent : P.initComponent.createSequence(function () {
            if (this.inputMask) {
                this._mst = parseMask(this.inputMask);  // masking state
                this.enableKeyEvents = true;
            }
        }),

        initEvents : P.initEvents.createSequence(function () {
            if (this._mst) {
                //
            }
        }),

        getCaret : function () {
            var begin, end, el = this.el.dom;
            if (el.setSelectionRange) {
                begin = el.selectionStart;
                end = el.selectionEnd;
            } else if (document.selection && document.selection.createRange) {
                var range = document.selection.createRange();
                begin = 0 - range.duplicate().moveStart('character', -100000);
                end = begin + range.text.length;
            }
            return { begin: begin, end: end };
        },

        onKeyDown : hook(P.onKeyDown, function (superFn, e) {
            superFn.call(this, e);
        }),

        onKeyPress : hook(P.onKeyPress, function (superFn, e) {
            superFn.call(this, e);
        }),

        preFocus : P.preFocus.createSequence(function () {
            if (this._mst) {
                this._mst.focusText = this.getRawValue();
            }
        })
    });

})(Ext.form.TextField);
